package rsi.mesoscopic.view;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;

public class DrawCanvas extends JPanel{

	/**
	 * Identifica objetos serializáveis.
	 */
	private static final long serialVersionUID = 1L;

	private BufferedImage animationBuffer;
	
	private BufferedImage buffer;
	
	private Canvas mContext;
	
	private boolean changed = false;
	
	private boolean drawing = false;
	
	private int fps = 20;
	
	private Color backgrounColor = Color.white;
	
	private List<View> mViews;
	
	public DrawCanvas(){
		mViews = new ArrayList<View>();
	}
	
	public void paint(Graphics g){
		super.paint(g);
		
		if(buffer == null){
			initBuffers(getWidth(), getHeight());
			changed = true;
			init();
			addComponentListener(mComponentListener);
		}
	}
	
	public BufferedImage getBufferImage(){
		return this.buffer;
	}
	
	public void init(){
	
		initBuffers(getWidth(), getHeight());
		
		Thread drawer = new Thread(new Runnable() {
			
			@Override
			public void run() {
				while(drawing){
					DrawCanvas.this.render();
					
					/*
					 * TODO código para evitar congelamentos
					 */
					
					try {
						Thread.sleep(1000/fps);
					} catch (InterruptedException e) {
						e.printStackTrace();
					} 
				}
			}
			
		});
		
		drawer.start();
	}
	
	public void render() {
		
		/*
		 *	Desenha os objetos na camada principal
		 */
		synchronized (this) {
			if(this.changed){
				renderScreenObjects();
				this.changed = false;
			}
		}
		
		repaint();
	}
	
	public void paintComponent(Graphics g){
		draw(g);
	}
	
	public void clear(){
		mContext.clean();
		
		synchronized(this) {
			changed = true;
		}
	}
	
	public void update() {
		synchronized(this) {
			changed = true;
		}
	}
	
	public void addView(View v) {
		mViews.add(v);
	}
	
	public void zoom(float value) {
		
		int width = (int)(getWidth()*value);
		int height = (int)(getHeight()*value);

		initBuffers(width, height);
		clear();
		
		Graphics2D g2 = mContext.getGraphics();
		
		g2.scale(value, value);

		setPreferredSize(new Dimension(width, height));
	}
	
	private void initBuffers(int width, int height) {
		
		synchronized (this) {
				
	        if(mContext!=null){
	        	((CanvasImpl)mContext).dispose();
	        	mContext = null;
	        }
	        if(buffer!=null){
	            buffer.flush();
	            buffer=null;
	        }
	        System.gc();
		        
			buffer = (BufferedImage) this.createImage(width, height);
	
			if(buffer == null){
				throw new IllegalStateException(
						"Não foi possível iniciar o buffer para double buffering.");
			}
			
			mContext = new CanvasImpl((Graphics2D)buffer.getGraphics());
			mContext.getGraphics().clipRect(0, 0, getWidth(), getHeight());
			
			drawing = true;
		}
	}
	
	private void renderScreenObjects() {
		mContext.clean();
		
		for(View v : mViews) {
			v.draw(mContext);
		}
	}
	
	protected void draw(Graphics g){
	
		Graphics2D panelGraphics = (Graphics2D) g;
		
		//Copia o buffer para a tela.
		if(panelGraphics != null){
			panelGraphics.drawImage(buffer, 0, 0, null);
			panelGraphics.drawImage(animationBuffer, 0, 0, null);
		}
	}
	
	protected class CanvasImpl extends Canvas{

		private Graphics2D mG2;
		
		public CanvasImpl(Graphics2D g2) {
			mG2 = g2;
		}
		
		@Override
		public Graphics2D getGraphics() {
			return mG2;
		}

		public void clean() {
			mG2.setColor(backgrounColor);
			mG2.fillRect(0, 0, getWidth(), getHeight());
		}
		
		public void dispose() {
			mG2.dispose();
			mG2 = null;
		}
	}
	
	private ComponentListener mComponentListener = new ComponentListener() {
		
		@Override
		public void componentShown(ComponentEvent e) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void componentResized(ComponentEvent e) {
			initBuffers(getWidth(), getHeight());
			update();
		}
		
		@Override
		public void componentMoved(ComponentEvent e) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void componentHidden(ComponentEvent e) {
			// TODO Auto-generated method stub
			
		}
	};
}
